/*  这个文件中提供统一的接口，例如：open，write
    等等，用来驱动前一个文件中描述的设备
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>

#define Major 243       /*主设备号*/
#define Dev_Name    "led"

static struct class *led_class; /*创建类*/

/*led的硬件信息*/
struct led_data
{
	unsigned int led_pin;
	unsigned int clk_regshift;

	unsigned int __iomem *va_dr;
	unsigned int __iomem *va_gdir;
	unsigned int __iomem *va_iomuxc_mux;
	unsigned int __iomem *va_ccm_ccgrx;
	unsigned int __iomem *va_iomux_pad;

	struct cdev led_cdev;               /*cdev就是char_device，也就是字符设备*/
};



ssize_t led_cdev_write(struct file * filp, const char __user * buf, size_t count, loff_t * lofft)
{
	printk("%s\n", __func__);
	unsigned long val = 0;
	unsigned long ret = 0;

	int tmp = count;

	struct led_data *cur_led = (struct led_data *)filp->private_data;

	kstrtoul_from_user(buf, tmp, 10, &ret);

	val = readl(cur_led->va_dr);
	if (ret == 0)
		val &= ~(0x01 << cur_led->led_pin);
	else
		val |= (0x01 << cur_led->led_pin);

	writel(val, cur_led->va_dr);
	*lofft += tmp;

	return tmp;
}

int led_cdev_open(struct inode * inode, struct file * filp)
{
	printk("%s\n", __func__);
	/*container_of函数的作用就是通过结构体中的一个成员变量在指针指向的内存中取出整个结构体的指针*/
	struct led_data *cur_led = (struct led_data *)container_of(inode->i_cdev,struct led_data,led_cdev);
	unsigned int val = 0;

	val = readl(cur_led->va_ccm_ccgrx);		/*剩下的都是寄存器操作*/
	val &= ~(3 << cur_led->clk_regshift);
	val |= (3 << cur_led->clk_regshift);
	writel(val, cur_led->va_ccm_ccgrx);

	writel(5, cur_led->va_iomuxc_mux);

	writel(0x1F838, cur_led->va_iomux_pad);

	val = readl(cur_led->va_gdir);
	val &= ~(1 << cur_led->led_pin);
	val |= (1 << cur_led->led_pin);
	writel(val, cur_led->va_gdir);

	val = readl(cur_led->va_dr);
	val |= (0x01 << cur_led->led_pin);
	writel(val, cur_led->va_dr);

	filp->private_data = cur_led;

	return 0;
}

int led_cdev_release(struct inode *inode, struct file * flip)
{
	return 0;	/*不需要有任何操作*/
}

static struct file_operations led_cdev_fops = {
	.open = led_cdev_open,
	.release = led_cdev_release,
	.write = led_cdev_write,
};


static struct platform_device_id led_pdev_ids[] = {
	{.name = "gpio_led"},
	{}};

MODULE_DEVICE_TABLE(platform, led_pdev_ids);

/*注意传入的参数就是匹配成功的设备*/
/*因为我们创建了三个总线设备，而且name相同，因此probe函数会被执行三次*/
int led_pdrv_probe(struct platform_device * pdev)
{
    struct led_data *cur_led;		/*这里不设置成全局变量的原因是一个系统上不止一个设备*/
	unsigned int *led_hwinfo;

	struct resource *mem_dr;
	struct resource *mem_gdir;
	struct resource *mem_iomuxc_mux;
	struct resource *mem_ccm_ccgrx;
	struct resource *mem_iomux_pad;

    dev_t my_dev;
    int ret=0;  /*返回值，用来判断程序是否正常运行*/

    printk("led platform driver probe\n");      /*提示以下进入了probe函数*/

	/*获取资源*/
	cur_led = devm_kzalloc(&pdev->dev,sizeof(struct led_data),GFP_KERNEL);	/*自动申请内存，设备删除的时候内存自动释放，不会造成内存泄漏*/
	if(cur_led == NULL)
		return -ENOMEM;	/*代表是内存申请的错误*/
	led_hwinfo = devm_kzalloc(&pdev->dev,sizeof(unsigned int),GFP_KERNEL);	/*向但与把这段内存与设备之间关联起来*/
	if(led_hwinfo == NULL)
		return -ENOMEM;

	/*获取寄存器偏移量（在设备文件中描述的）*/
	led_hwinfo = dev_get_platdata(&pdev->dev);
	cur_led->led_pin = led_hwinfo[0];
	cur_led->clk_regshift = led_hwinfo[1];

	/*利用函数platform_get_resource可以获取到各个寄存器的地址*/
	/*第二个参数是读取的数据类型，第三个参数是resource数组的序号*/
	mem_dr = platform_get_resource(pdev,IORESOURCE_MEM,0);
	mem_gdir = platform_get_resource(pdev,IORESOURCE_MEM,1);
	mem_iomuxc_mux = platform_get_resource(pdev,IORESOURCE_MEM,2);
	mem_ccm_ccgrx = platform_get_resource(pdev,IORESOURCE_MEM,3);
	mem_iomux_pad = platform_get_resource(pdev,IORESOURCE_MEM,4);

	/*虚拟地址转寄存器地址*/
	cur_led->va_dr =
		devm_ioremap(&pdev->dev, mem_dr->start, resource_size(mem_dr));
	cur_led->va_gdir =
		devm_ioremap(&pdev->dev, mem_gdir->start, resource_size(mem_gdir));
	cur_led->va_iomuxc_mux =
		devm_ioremap(&pdev->dev, mem_iomuxc_mux->start,resource_size(mem_iomuxc_mux));
	cur_led->va_ccm_ccgrx =
		devm_ioremap(&pdev->dev, mem_ccm_ccgrx->start,resource_size(mem_ccm_ccgrx));
	cur_led->va_iomux_pad =
		devm_ioremap(&pdev->dev, mem_iomux_pad->start,resource_size(mem_iomux_pad));

	/*注册字符设备*/
	my_dev = MKDEV(Major,pdev->id);		/*设备号,这个时候就可以看出主设备号和副设备号的关系了*/
	register_chrdev_region(my_dev,1, "led_cdev");
	cdev_init(&cur_led->led_cdev,&led_cdev_fops);
	cdev_add(&cur_led->led_cdev, my_dev, 1);

	device_create(led_class, NULL, my_dev, NULL, "led ""%d", pdev->id);

	/*platform_set_drvdata函数，将LED数据信息存入在平台驱动结构体中pdev->dev->driver_data中*/
	/*这个函数很重要*/
	platform_set_drvdata(pdev, cur_led);

    return ret;
}

/*dev_t是设备号，是把主设备号和自设备号组合一下变成dev_t这个新类型，实际上就是个16位整形*/
int led_pdrv_remove(struct platform_device * pdev)
{
	printk("remove the pdrv ! \r\n");
	dev_t my_dev;
	my_dev = MKDEV(Major,pdev->id);			/*这个是dev_t*/
	struct led_data *cur_data = (struct led_data *)platform_get_drvdata(pdev);
	/*删除cdev*/
	cdev_del(&cur_data->led_cdev);
	/*删除dev_t*/
	device_destroy(led_class,my_dev);
	/*注销字符设备*/
	unregister_chrdev_region(my_dev,1);
	return 0;
}

// led_pdrv中定义了两种匹配模式
// 平台总线匹配过程中 ，只会根据id_table中的name值进行匹配，若和平台设备的name值相等，则表示匹配成功； 反之，则匹配不成功，表明当前内核没有该驱动能够支持的设备。
//创建的平台设备驱动结构体
static struct platform_driver led_pdrv = {

	.probe = led_pdrv_probe,
	.remove = led_pdrv_remove,
	.driver.name = "gpio_led",
	.id_table = led_pdev_ids,
};

__init int led_pdrv_init(void)
{
    printk("pdrv init !\r\n");
	/*创建类*/
	led_class=class_create(THIS_MODULE, "my_leds");
    platform_driver_register(&led_pdrv);
    return 0;
}

__exit void led_pdrv_exit(void)
{
    printk("pdrv exit !\r\n");
	class_destroy(led_class);
    platform_driver_unregister(&led_pdrv);
}

module_init(led_pdrv_init);
module_exit(led_pdrv_exit);





